package net.scribblemedia.candle.data.pattern;

import static java.lang.Math.abs;
import static net.scribblemedia.candle.data.pattern.PatternChangeType.FLAT_CHANGE;
import static net.scribblemedia.candle.data.pattern.PatternChangeType.IRRELEVANT_CHANGE;
import static net.scribblemedia.candle.data.pattern.PatternChangeType.SIGNIFICANT_CHANGE;
import static net.scribblemedia.candle.util.ScribbleCollectionUtils.lastElement;
import static org.apache.commons.lang3.ArrayUtils.toPrimitive;
import static org.encog.ml.data.market.MarketDataType.ADJUSTED_CLOSE;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.scribblemedia.candle.data.trainingset.TrainingSetHolder;

import org.encog.ml.data.market.loader.LoadedMarketData;

public class PatternWindowService {

	public LoadedMarketData getMarketDataForCurrentDay(PatternWindow patternWindow) {
		return lastElement(patternWindow.getTailData());
	}

	public double getEndOfNoseGain(PatternWindow patternWindow, double endOfNoseGainPeriodDays) throws InsufficientNoseDataException {
		LoadedMarketData marketDataForCurrentDay = getMarketDataForCurrentDay(patternWindow);
		double adjustedCloseForCurrentDay = marketDataForCurrentDay.getData(ADJUSTED_CLOSE);
		
		int lastElementIndex = patternWindow.getNoseData().size() - 1;
		
		if (endOfNoseGainPeriodDays > lastElementIndex) {
			throw new InsufficientNoseDataException();
		}
		
		double summedAdjustedCloseForEndOfNose = 0;
		ArrayList<LoadedMarketData> noseList = new ArrayList<LoadedMarketData>(patternWindow.getNoseData());
		
		for (int i = lastElementIndex; i > lastElementIndex - endOfNoseGainPeriodDays; i--) {
			summedAdjustedCloseForEndOfNose += noseList.get(i).getData(ADJUSTED_CLOSE);
		}
		
		return (summedAdjustedCloseForEndOfNose / endOfNoseGainPeriodDays) - adjustedCloseForCurrentDay;
	}
	
	public Map<CandleStickType, Integer> getTailCandleStickClassificationCount(PatternWindow patternWindow) {
		Map<CandleStickType, Integer> classificationMap = buildEmptyCandleStickClassificationMap();
		for (LoadedMarketData tailDataItem : patternWindow.getTailData()) {
			CandleStick candleStick = new CandleStick(tailDataItem);
			CandleStickType candleStickType = candleStick.determineType();
			int currentCount = classificationMap.get(candleStickType);
			classificationMap.put(candleStickType, currentCount + 1);
		}
		return classificationMap;
	}

	private HashMap<CandleStickType, Integer> buildEmptyCandleStickClassificationMap() {
		HashMap<CandleStickType, Integer> classificationMap = new HashMap<CandleStickType, Integer>();
		for (CandleStickType type : CandleStickType.values()) {
			classificationMap.put(type, 0);
		}
		return classificationMap;
	}
	
	public TrainingSetHolder buildTrainingSet(Collection<PatternWindow> patternWindows, double minimumPercentageChangeForPatternInclusion, double noseSpreadDays, double flatGainThresholdPercentage) {
		List<double[]> inputDataSets = new ArrayList<double[]>();
		List<double[]> expectedOutcomes = new ArrayList<double[]>();
		
		for (PatternWindow patternWindow : patternWindows) {
			double endOfNoseGain;
			try {
				endOfNoseGain = getEndOfNoseGain(patternWindow, noseSpreadDays);
				
				PatternChangeType patternChangeType = isIndicitivePatternWindow(patternWindow, minimumPercentageChangeForPatternInclusion, noseSpreadDays, flatGainThresholdPercentage);
				if (IRRELEVANT_CHANGE != patternChangeType) {
					inputDataSets.add(normalise(createInputDataFromPatternWindow(patternWindow)));
					
					double sanitisedExpectedOutcome;
					
					if (endOfNoseGain > 0) {
						sanitisedExpectedOutcome = 1;
					} else if (endOfNoseGain <= (flatGainThresholdPercentage / 100)) {
						sanitisedExpectedOutcome = 0;
					} else {
						sanitisedExpectedOutcome = -1;
					}
					
					expectedOutcomes.add(new double[] { sanitisedExpectedOutcome });
				}
			} catch (InsufficientNoseDataException ignored) { }
		}
		return new TrainingSetHolder(inputDataSets.toArray(new double[][] {}), expectedOutcomes.toArray(new double[][] {}));
	}
	
	public PatternChangeType isIndicitivePatternWindow(PatternWindow patternWindow, double minimumPercentageChangeForPatternInclusion, double noseSpreadDays, double flatGainThresholdPercentage) {
		double endOfNoseGain;
		try {
			endOfNoseGain = getEndOfNoseGain(patternWindow, noseSpreadDays);
			double endOfTailValue = lastElement(patternWindow.getTailData()).getData(ADJUSTED_CLOSE);

			if (((abs(endOfNoseGain) / abs(endOfTailValue)) >= (minimumPercentageChangeForPatternInclusion / 100))) {
				return SIGNIFICANT_CHANGE;
			} else if (((abs(endOfNoseGain) / abs(endOfTailValue)) <= (flatGainThresholdPercentage / 100))) {
				return FLAT_CHANGE;
			}
			
			return IRRELEVANT_CHANGE; 
			
		} catch (InsufficientNoseDataException e) {
			return PatternChangeType.IRRELEVANT_CHANGE;
		}
	}

	private double[] normalise(double[] array) {
		double maxValue = 0d;
		for (double currentValue : array) {
			if (currentValue > maxValue) {
				maxValue = currentValue;
			}
		}

		for (int i = 0; i < array.length; i++) {
			array[i] = array[i] / maxValue;
		}
		
		return array;
	}

	private double[] createInputDataFromPatternWindow(PatternWindow patternWindow) {
		List<Double> patternInputData = new ArrayList<Double>();
		Map<CandleStickType, Integer> classificationCount = getTailCandleStickClassificationCount(patternWindow);
		for (CandleStickType candleStickType : CandleStickType.values()) {
			Integer candleValue = classificationCount.get(candleStickType);
			patternInputData.add(new Double(candleValue));
		}
		return toPrimitive(patternInputData.toArray(new Double[] {}));
	}
}
